package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.*;
import com.heima.article.service.ApArticleService;
import com.heima.feigns.behavior.BehaviorFeign;
import com.heima.feigns.user.UserFeign;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.dtos.CollectionBehaviorDto;
import com.heima.model.article.pojos.*;
import com.heima.model.article.vo.HotArticleVo;
import com.heima.model.behavior.dtos.ApArticleRelationDto;
import com.heima.model.behavior.pojos.ApBehaviorEntry;
import com.heima.model.common.constants.ArticleConstants;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.app.ArticleVisitStreamMess;
import com.heima.model.user.pojos.ApUser;
import com.heima.model.user.pojos.ApUserFollow;
import com.heima.utils.threadlocal.AppThreadLocalUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;


@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired  //作者表
    AuthorMapper authorMapper;
    @Autowired
    ApArticleMapper articleMapper;
    @Autowired  //文章配置
    ApArticleConfigMapper apArticleConfigMapper;
    @Autowired  //文章内容
    ApArticleContentMapper apArticleContentMapper;
    @Autowired
    ApCollectionMapper collectionMapper;
    @Autowired
    KafkaTemplate kafkaTemplate;

    @Value("${file.oss.web-site}")
    String webSite;

    @Autowired
    BehaviorFeign behaviorFeign;

    @Autowired
    UserFeign userFeign;


    /**
     * 加载文章详情的初始化配置信息，比如关注、喜欢、不喜欢等
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult loadArticleBehavior(ArticleInfoDto dto) {
        ApUser user = AppThreadLocalUtils.getUser();
        if (user == null) {
            return ResponseResult.okResult(new HashMap<>());
        }
        if (dto == null || dto.getArticleId() == null || dto.getAuthorId() == null) {
            return ResponseResult.okResult(new HashMap<>());
        }
        ApBehaviorEntry entry = behaviorFeign.findByUserIdOrEquipmentId(user.getId(), 0);
        if (entry == null) {
            return ResponseResult.okResult(new HashMap<>());
        }
        //根据文章id查询当前的文章的作者id
        ApAuthor apAuthor = authorMapper.selectById(dto.getAuthorId());
        if (apAuthor == null) {
            return ResponseResult.okResult(new HashMap<>());
        }
        Map<String, Object> resultMap = new HashMap();

        ApUserFollow apUserFollow = userFeign.findByUserIdAndFollowId(user.getId(), apAuthor.getUserId());
        //当前插寻的表格不为空的话,就证明已经关注了当前用户
        if (apUserFollow != null) {
            resultMap.put("isfollow", true);
        }
        //查询当前收藏状态
        ApCollection apCollection = collectionMapper.selectOne(Wrappers.<ApCollection>lambdaQuery()
                .eq(ApCollection::getArticleId, dto.getArticleId())
                .eq(ApCollection::getEntryId, entry.getId()));
        if (apCollection != null) {
            resultMap.put("iscollection", true);
        }
        //查询是否点赞和不喜欢

        ApArticleRelationDto relation = new ApArticleRelationDto();
        relation.setArticleId(dto.getArticleId());
        relation.setEntryId(entry.getId());
        relation.setType((short) 1);  //用户终端
        Map apArticleRelation = behaviorFeign.findApArticleRelation(relation);
        resultMap.putAll(apArticleRelation);
        return ResponseResult.okResult(resultMap);
    }

    @Override
    public ResponseResult collectionArticle(CollectionBehaviorDto dto) {
        //收藏这个相关信息   EntryID是文章id
        if (dto == null || dto.getEntryId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        ApUser user = AppThreadLocalUtils.getUser();
        if (user == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN, "沒有登录不能够收藏文章");
        }
        //查询当前的关系实体
        ApBehaviorEntry entry = behaviorFeign.findByUserIdOrEquipmentId(user.getId(), 0);
        if (entry == null) {
            //没有相关实体,不能进行收藏
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN, "没有相关的关系实体,不能收藏");
        }
        ApCollection apCollection = collectionMapper.selectOne(Wrappers.<ApCollection>lambdaQuery()
                .eq(ApCollection::getArticleId, dto.getEntryId())
                .eq(ApCollection::getEntryId, entry.getId()));
        if (apCollection == null && dto.getType().intValue() == 1) {
            //如果收藏为空并且为取消收藏的话就返回
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_EXIST, "不能够收藏");
        }
        //
        if (apCollection != null && apCollection.getOperation() == dto.getOperation().intValue()) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_EXIST, "不能够重复操作");
        }
        ApArticle apArticle = articleMapper.selectById(dto.getEntryId());
        if (apArticle == null) {
            //如果这个文章为空的话返回false
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "未查询到相关文章");
        }

        if (apCollection == null) {
            //判断并插入
            apCollection = new ApCollection();
            apCollection.setArticleId(dto.getEntryId());
            apCollection.setCollectionTime(new Date());
            apCollection.setEntryId(entry.getId());
            apCollection.setOperation(dto.getOperation().intValue());
            apCollection.setType((short) 0);
            apCollection.setPublishedTime(new Date());
            apCollection.setArticleId(apArticle.getId());
            collectionMapper.insert(apCollection);
        } else {
            //关注或者取消关注
            apCollection.setOperation(dto.getOperation().intValue());
            collectionMapper.updateById(apCollection);
        }
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);

    }

    /**
     * 查询文章详情
     * @param dto 文章id
     * @return
     */
    @Override
    public ResponseResult loadArtilceInfo(ArticleInfoDto dto) {
        if (dto == null || dto.getArticleId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //根据id返回文章信息
        ApArticleConfig articleConfig = apArticleConfigMapper.selectOne(Wrappers.
                <ApArticleConfig>lambdaQuery().eq(ApArticleConfig::getArticleId, dto.getArticleId()));
        if (articleConfig == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "没有查询到相关作者信息");
        }
        ApArticleContent articleContent = apArticleContentMapper.selectOne(Wrappers.
                <ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getArticleId()));
        if (articleContent == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "没有查询到相关作者信息");
        }
        Map<String, Object> map = new HashMap<>();
        map.put("config", articleConfig);
        map.put("content", articleContent);

        return ResponseResult.okResult(map);
    }

    /**
     * 根据参数加载文章列表
     * @param loadtype 1为加载更多  2为加载最新
     * @param dto
     * @return
     */
    @Override
    public ResponseResult load(Short loadtype, ArticleHomeDto dto) {
        if (dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        if (dto.getMaxBehotTime() == null) {
            dto.setMaxBehotTime(new Date());
        }
        if (dto.getMinBehotTime() == null) {
            dto.setMinBehotTime(new Date());
        }
        //参数校验
        if (dto.getSize() == null || dto.getSize() <= 0 || dto.getSize() > 50) {
            dto.setSize(10);
        }
        if (dto.getTag() == null) {
            dto.setTag("__all__");
        }
        if (!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)
                && !loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)) {
            loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;
        }
        List<ApArticle> articleList = articleMapper.loadArticleList(dto, loadtype);
        ResponseResult result = ResponseResult.okResult(articleList);
        result.setHost(webSite);
        return result;
    }

    /**
     * 保存或修改文章
     * @param articleDto
     * @return
     */
    @Override
    public ResponseResult saveArtilce(ArticleDto articleDto) {
        ApAuthor author = authorMapper.selectOne(Wrappers.<ApAuthor>lambdaQuery()
                .eq(ApAuthor::getName, articleDto.getAuthorName()));
        if (author == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "查询不到该作者");
        }
        ApArticle article = new ApArticle();
        BeanUtils.copyProperties(articleDto, article);
        if (articleDto.getId() == null) {   //新增操作
            //1 保存article 文章数据， 设置作者id
            article.setAuthorId(author.getId().longValue());
            // 普通文章
            article.setComment(0);
            article.setViews(0);
            article.setCollection(0);
            article.setLikes(0);
            // 保存并返回ID
            save(article);
            //2 保存article_config 数据
            ApArticleConfig articleConfig = new ApArticleConfig();
            articleConfig.setIsComment(true);
            articleConfig.setArticleId(article.getId());
            articleConfig.setIsDelete(false);
            articleConfig.setIsDown(false);
            articleConfig.setIsForward(true);
            apArticleConfigMapper.insert(articleConfig);
            //3 保存文章article_content 数据
            ApArticleContent articleContent = new ApArticleContent();
            articleContent.setArticleId(article.getId());
            articleContent.setContent(articleDto.getContent());
            apArticleContentMapper.insert(articleContent);
        } else {
            //修改数据库的相关操作
            ApArticle apArticl = getById(articleDto.getId());
            if (apArticl == null) {
                //如果这个数据值为空的话,记录日志抛出异常
                log.error("修改文章内容时没有查询到相关文章");
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "没有查询到相关文章");
            }
            updateById(article);

            //修改文章内容  根据文章id来查询文章内容的相关信息
            ApArticleContent articleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery()
                    .eq(ApArticleContent::getArticleId, articleDto.getId()));
            if (articleContent == null) {
                //如果这个数据值为空的话,记录日志抛出异常
                log.error("修改文章内容时没有查询到相关文章");
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "没有查询到相关文章");
            }
            articleContent.setContent(articleDto.getContent());
            apArticleContentMapper.updateById(articleContent);
        }

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("id", article.getId().toString());
        map.put("publishTime", article.getPublishTime());
        map.put("layout", article.getLayout());
        map.put("images", article.getImages());
        map.put("authorId", article.getAuthorId());
        map.put("title", article.getTitle());
        map.put("tag", "article");
        kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(map));

        //4 返回结果  文章保存后的主键 ID
        return ResponseResult.okResult(article.getId());
    }


    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * 1.更新热点文章分值操作
     * 2.更新当前的redis缓存操作
     * @param mess
     */
    @Override
    public void updateApArticle(ArticleVisitStreamMess mess) {
        ApArticle apArticle = getById(mess.getArticleId());
        if (mess.getView() != 0) {
            int view = (int) (apArticle.getViews() == null ? mess.getView() : mess.getView() + apArticle.getViews());
            apArticle.setViews(view);
        }
        if (mess.getLike() != 0) {
            int like = (int) (apArticle.getLikes() == null ? mess.getLike() : mess.getLike() + apArticle.getLikes());
            apArticle.setLikes(like);
        }
        if (mess.getComment() != 0) {
            int comment = (int) (apArticle.getComment() == null ? mess.getComment() : mess.getComment() + apArticle.getComment());
            apArticle.setComment(comment);
        }
        if (mess.getCollect() != 0) {
            int collection = (int) (apArticle.getCollection() == null ? mess.getCollect() : mess.getCollect() + apArticle.getCollection());
            apArticle.setCollection(collection);
        }
        updateById(apArticle);

        //3 计算本次操作为文章增加的分值   
        // 策略:每日凌晨1:00所有文章分值重新计算,当日操作(点赞,评论,关注,收藏)任意文章分值为默认权重的三倍
        Integer score = computeThisOperationScore(mess);

        //当前分值已经乘了三
        //4 获取当前文章频道的缓存,对比更新当前缓存
        //形参(redis索引(String),当前操作分值(Integer),当前文章(ApArticle),)

        getAndUpdateArticleCache(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId(), score, apArticle);
        log.info("getAndUpdateArticleCache 对应频道的缓存 is success");

        //5 更新推荐列表的缓存

        getAndUpdateArticleCache(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG, score, apArticle);
        log.info("getAndUpdateArticleCache 综合频道的缓存 is success");


    }

    /**
     *
     * @param redisIndex    redis索引
     * @param score         本次操作所得的分数
     * @param apArticle     当前操作的文章
     */
    private void getAndUpdateArticleCache(String redisIndex, Integer score, ApArticle apArticle) {
        String stringList = StringEscapeUtils.unescapeJava(
                redisTemplate.opsForValue()
                .get(redisIndex));
        log.info("---------------------------------------------------------------------------");
        log.info(stringList);
        log.info("---------------------------------------------------------------------------");

//        String jsonString = JSON.toJSONString(stringList);
        List<HotArticleVo> hotArticleVos = JSON.parseArray(stringList, HotArticleVo.class);
        if (hotArticleVos == null || hotArticleVos.size() == 0) {
            hotArticleVos = new ArrayList<HotArticleVo>();
            HotArticleVo hotArticleVo = new HotArticleVo();
            BeanUtils.copyProperties(apArticle, hotArticleVo);
            apArticle.setFlag((byte) 1);
            //todo:這個地方是不是多加了一部分分值
            hotArticleVo.setScore(computeScore(apArticle) + score);
            hotArticleVos.add(hotArticleVo);
            articleMapper.updateById(apArticle);
            redisTemplate.opsForValue().set(redisIndex,
                    JSON.toJSONString(hotArticleVos));
            log.info("***********************************当前频道缓存热点文章为一篇:更新Redis缓存成功******************************************");
            return;
        }


        log.info("当前频道redis索引为------->" + redisIndex);
        log.info("当前频道索引对应字段为,未更新前:{}", hotArticleVos);
        //遍历当前获取的redis集合  判断当前文章是否已经是热点文章
        boolean flag = true;
        for (HotArticleVo hotArticleVo : hotArticleVos) {
            if (hotArticleVo.getId().equals(apArticle.getId())) {
                log.info("******************************当前操作所得分数为,{}", score, "**************************************");
                hotArticleVo.setScore(hotArticleVo.getScore() + score);
                flag = false;
                break;
            }
        }
        log.info("当前频道索引对应字段为,更新后:{}", hotArticleVos);
//        hotArticleVos.stream().forEach(hotArticleVo -> {
//            if (apArticle.getId().equals(hotArticleVo.getId())) {
//                //如果是当前的热点文章,分数更新,替换,重新排序
//                log.info("******************************当前操作所得的分数为,{}", score, "**************************************");
//                hotArticleVo.setScore(score + hotArticleVo.getScore());
//
//                //为什么 Lambda 表达式(匿名类) 不能访问非 final 的局部变量呢？
//                // 因为实例变量存在堆中，而局部变量是在栈上分配，
//                // Lambda 表达(匿名类) 会在另一个线程中执行。
//                // 如果在线程中要直接访问一个局部变量，可能线程执行时该局部变量已经被销毁了，
//                // 而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝。
//
////                flag = true;   //lambda表达式中不能够对局部变量进行更改值  变量必须为final修饰
//            }
//        });
        //如果当前文章不是热点文章,和最后一个数据分值比较一下,决定替换或者不变

        //得到重新排序后的集合,将重新排序后的集合保存到redis缓存中去

        hotArticleVos = hotArticleVos.stream()
                .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                .limit(5)
                .collect(Collectors.toList());


        //todo:不确定当前的分数计算方法是否正确
        Integer countScore = computeScore(apArticle) + score;
        log.info("countScore 总得分{}",countScore);

        //两个逻辑二选一   上面的没有执行才执行下面的
        if (flag) {
            if (hotArticleVos.size() == 5) {
                // 获取最后一个元素和当前文章的分值作比较

                HotArticleVo hotArticleVo = hotArticleVos.get(hotArticleVos.size() - 1);
                if (countScore > hotArticleVo.getScore()) {
                    hotArticleVo.setFlag((byte)0);
                    updateById(hotArticleVo);
                    hotArticleVos.remove(hotArticleVo);
                    HotArticleVo hotArticle = new HotArticleVo();
                    BeanUtils.copyProperties(apArticle, hotArticle);
                    hotArticle.setScore(countScore);
                    hotArticleVos.add(hotArticle);
                }
            } else { // 热点文章不足5条，添加到热点文章列表中
                HotArticleVo hot = new HotArticleVo();
                BeanUtils.copyProperties(apArticle, hot);
                apArticle.setFlag((byte)1);
                updateById(apArticle);
                hot.setScore(countScore);
                hotArticleVos.add(hot);
            }
        }

        //更新当前缓存内的数据集合
        redisTemplate.opsForValue().set(redisIndex,
                JSON.toJSONString(hotArticleVos));
        log.info("***********************************更新Redis缓存成功******************************************");

    }



    /**
     * 2.1计算本次操作文章所得分值算法
     * @param mess
     * @return
     */
    private Integer computeThisOperationScore(ArticleVisitStreamMess mess) {
        int score = 0;
        if (mess.getView() != 0) {
            score += mess.getView() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        if (mess.getCollect() != 0) {
            score += mess.getCollect() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        if (mess.getComment() != 0) {
            score += mess.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if (mess.getLike() != 0) {
            score += mess.getLike() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        return score * 3;
    }

    /**
     * 2.计算文章分值算法
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        int score = 0;
        // 阅读 1
        if (apArticle.getViews() != null) {
            score += apArticle.getViews() * ArticleConstants.HOT_ARTICLE_VIEW_WEIGHT;
        }
        // 点赞 3
        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        // 评论 5
        if (apArticle.getComment() != null) {
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        // 收藏 8
        if (apArticle.getCollection() != null) {
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }

    /**
     * 根据参数加载文章列表  v2
     * @param loadtypeLoadMore
     * @param dto
     * @param firstPage
     * @return
     */
    @Override
    public ResponseResult load2(Short loadtypeLoadMore, ArticleHomeDto dto, boolean firstPage) {
        if(firstPage){
            String jsonStr = (String) redisTemplate.opsForValue().get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
            if(StringUtils.isNotBlank(jsonStr)){
                List<HotArticleVo> hotArticleVoList = JSON.parseArray(jsonStr, HotArticleVo.class);
                if(!hotArticleVoList.isEmpty()&& hotArticleVoList.size() > 0){
                    ResponseResult responseResult = ResponseResult.okResult(hotArticleVoList);
                    responseResult.setHost(webSite);
                    return responseResult;
                }
            }
        }
        return load(loadtypeLoadMore,dto);
    }

}